<?php
/**
 * CLI 命令行处理
 *
 * @author     Akon(番茄红了) <aultoale@gmail.com>
 */
abstract class CLI
{

    /**
     * 标识 CLI 是否已初始化
     *
     * @var bool
     */
    protected static $_init = FALSE;

    /**
     * 菜单分隔符
     *
     * @var string
     */
    public static $menu_separator = '=====================================================';

    /**
     * CLI 模式初始化
     *
     * @return void
     * @throws Kohana_Exception
     */
    public static function init()
    {
        if (CLI::$_init)
        {
            // 已初始化，直接返回
            return ;
        }

        // 标识为已初始化
        CLI::$_init = TRUE;

        // 关闭 Kohana 可视化错误输出
        Kohana::$profiling = FALSE;

        // 错误常量定义
        if (defined('E_DEPRECATED'))
        {
            Kohana::$php_errors[E_DEPRECATED] = 'Deprecated';
        }

        // 关闭 Kohana 异常处理接口
        Kohana::$errors = FALSE;

        // 设置异常处理接口
        set_exception_handler(array('CLI_Exception', 'handler'));

        // 设置错误处理接口
        set_error_handler(array('Kohana', 'error_handler'));

        // 析构方法，以处理 E_FATAL 捕捉到的错误
        register_shutdown_function(array('CLI', 'shutdown_handler'));

        // 通知 Kohana 运行模式为 CLI
        Kohana::$is_cli = (PHP_SAPI === 'cli');
        if ( ! Kohana::$is_cli)
        {
            throw new Kohana_Exception('只允许在 CLI 模式下运行');
        }

        // 通知 Kohana 当前是否在 windows 平台运行
        Kohana::$is_windows = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN');

        // 使用默认的缓存目录
        Kohana::$cache_dir = TMPPATH.'cache';

        if ( ! is_dir(Kohana::$cache_dir))
        {
            try
            {
                // 创建缓存目录
                mkdir(Kohana::$cache_dir, 0755, TRUE);

                // 设置目录权限
                chmod(Kohana::$cache_dir, 0755);
            }
            catch (Exception $e)
            {
                throw new Kohana_Exception('创建缓存目录失败 :dir',
                    array(':dir' => Debug::path(Kohana::$cache_dir)));
            }
        }

        if ( ! is_writable(Kohana::$cache_dir))
        {
            throw new Kohana_Exception('缓存目录 :dir 未开启写权限',
                array(':dir' => Debug::path(Kohana::$cache_dir)));
        }

        if (function_exists('mb_internal_encoding'))
        {
            // 为 MB 扩展设置默认编码格式
            mb_internal_encoding(Kohana::$charset);
        }

        // 通知 Kohana 是否启用了 magic_quotes
        Kohana::$magic_quotes = (bool) get_magic_quotes_gpc();

        // 设置 Log 实例
        Kohana::$log = Kohana_Log::instance()->attach(new Kohana_Log_File(APPROOT.'script/logs'));

        // 设置 Config 实例
        Kohana::$config = Kohana_Config::instance()->attach(new Kohana_Config_File);
    }

    /**
     * 运行一个 cli 实例
     *
     * @param  array  $argv
     * @return void
     */
    public static function run(array $argv)
    {
        if ( ! CLI::$_init)
        {
            // 执行初始化
            CLI::init();
        }

        try
        {
            // 获取参数
            $args = new CLI_Args($argv);
            $command = strtolower((string) $args);

            // 获取类名称
            $class = 'CLI_'.implode('_', array_map('ucfirst', explode('_', $command)));

            if ( ! class_exists($class))
            {
                throw new Kohana_Exception('未知的脚本命令 :command',
                    array(':command' => $command));
            }

            // 实例化脚本
            $object = new $class($args);

            if ( ! $object instanceof CLI)
            {
                throw new Kohana_Exception('CLI脚本类 :class 必须从 CLI 继承',
                    array(':class' => $class));
            }

            // 运行脚本主体
            return $object->main();
        }
        catch (Exception $e)
        {
            throw new CLI_Exception($e->getMessage(), $e->getCode(), $e->getFile(), $e->getLine());
        }
    }

    /**
     * Kohana::init() 析构方法
     *
     * @return  void
     */
    public static function shutdown_handler()
    {
        if ( ! CLI::$_init)
        {
            // 未初始化，直接返回
            return;
        }

        try
        {
            if (Kohana::$caching === TRUE AND Kohana::$_files_changed === TRUE)
            {
                // Write the file path cache
                Kohana::cache('Kohana::find_file()', Kohana::$_files);
            }
        }
        catch (Exception $e)
        {
            // Pass the exception to the handler
            CLI_Exception::handler($e);
        }

        if ($error = error_get_last() AND in_array($error['type'], Kohana::$shutdown_errors))
        {
            // Fake an exception for nice debugging
            CLI_Exception::handler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));

            // Shutdown now to avoid a "death loop"
            exit(1);
        }
    }

    /**
     * 文本输出
     *
     * @param  string  $string
     */
    public static function output($string)
    {
        if (Kohana::$is_windows)
        {
            // 输出为 GB2312 格式
            $string = mb_convert_encoding($string, 'GB2312', 'UTF-8');
        }

        echo $string;
    }

    /**
     * 获取输入文本
     *
     * @param  string  $message
     * @return string
     */
    public static function input($message = '')
    {
        CLI::output($message);
        $input = trim(fgets(STDIN));

        if (Kohana::$is_windows)
        {
            $input = mb_convert_encoding($input, 'UTF-8', 'GB2312');
        }

        return $input;
    }

    /**
     * 确认操作执行
     *
     * @param  string   $message
     * @param  array    $inputs
     * @param  bool     $ignore
     * @return int
     */
    public static function choice($message, array $inputs = array('y', 'n'), $ignore = true)
    {
        do {
            $choice = CLI::input($message.' ('.implode(',', $inputs).')：');

            // 忽略大写小
            if ($ignore) {
                $choice = strtolower($choice);
                $inputs = array_map('strtolower', $inputs);
            }

            // 输入正确
            if (in_array($choice, $inputs)) {
                return $choice;
            }
        } while(true);
    }

    /**
     * 输出选择菜单
     *
     * @param  array   $options
     * @return int
     */
    public static function menu(array $options)
    {
        $output = "\n".CLI::$menu_separator."\n";
        $inputs = array();
        foreach ($options as $key => $option) {
            $output .= "\n{$key}、{$option}\n";
            $inputs[] = $key;
        }
        $output .= "\n".CLI::$menu_separator."\n\n";

        return CLI::choice($output."请选择要执行的操作", $inputs);
    }

    /**
     * 参数数据
     *
     * @var CLI_Args
     */
    protected $args = NULL;

    /**
     * 构造方法
     *
     * @param  CLI_Args  $args
     * @return void
     */
    public function __construct(CLI_Args $args = NULL)
    {
        $this->args = $args;
    }

    /**
     * 脚本主方法
     *
     * @return void
     */
    abstract public function main();

    /**
     * 输出脚本名称
     *
     * @return string
     */
    public function __toString()
    {
        return str_ireplace('CLI', __CLASS__);
    }

}